#source https://github.com/hyperledger/indy-node/blob/master/scripts/read_ledger

import argparse
import logging
import os
import shutil

from pathlib import Path

from common.serializers.json_serializer import JsonSerializer
from plenum.common.ledger import Ledger
from plenum.common.constants import HS_ROCKSDB

from indy_common.config_util import getConfig
from common.serializers.serialization import ledger_txn_serializer

logging.root.handlers = []
logger = logging.getLogger()
logger.propagate = False
logger.disabled = True
_DATA = 'data'

# TODO: Replace with constant from config
postfix = '_transactions'


def read_args():
    parser = argparse.ArgumentParser(
        description="Read ledger transactions")

    parser.add_argument('--type', required=True,
                        help='Ledger type (pool, domain, config)')
    parser.add_argument(
        '--frm',
        required=False,
        default=None,
        help="read all transactions starting from (beginning by default)")
    parser.add_argument('--to', required=False, default=100,
                        help="read all transactions till (100 by default)")
    parser.add_argument('--seq_no', required=False,
                        help="read a particular transaction")
    parser.add_argument('--count', required=False, action='store_true',
                        help="returns the number of txns in the given ledger")
    parser.add_argument('--node_name', required=False, help="Node's name")
    parser.add_argument('--serializer', required=False, default='json',
                        help="How to represent the data (json by default)")
    parser.add_argument('--network', required=False, type=str,
                        help="Network name to read ledger from")

    return parser.parse_args()


def get_ledger_dir(node_name, network):
    config = getConfig()
    _network = network if network else config.NETWORK_NAME
    ledger_base_dir = config.LEDGER_DIR
    if node_name:
        # Build path to data if --node_name was specified
        ledger_data_dir = os.path.join(ledger_base_dir, _network, _DATA, node_name)
    else:
        ledger_data_dir = os.path.join(ledger_base_dir, _network, _DATA)
        if os.path.exists(ledger_data_dir):
            dirs = os.listdir(ledger_data_dir)
            if len(dirs) == 0:
                print("Node's 'data' folder not found: {}".format(ledger_data_dir))
                exit()
            # --node_name parameter was not set, therefore we can choose first Node name in data dir
            ledger_data_dir = os.path.join(ledger_data_dir, dirs[0])
    if not os.path.exists(ledger_data_dir):
        print("No such file or directory: {}".format(ledger_data_dir))
        print("Please check, that network: '{}' was used ".format(_network))
        exit()

    return ledger_data_dir


def get_storage(type_, ledger_data_dir):
    config = getConfig()

    storage_name = None
    if type_ == 'pool':
        storage_name = config.poolTransactionsFile
    elif type_ == 'domain':
        storage_name = config.domainTransactionsFile
    elif type_ == 'config':
        storage_name = config.configTransactionsFile
    elif type_ in get_additional_storages(ledger_data_dir):
        storage_name = type_ + postfix
    else:
        print("Unknown ledger type: {}".format(type_))
        exit()

    return Ledger._defaultStore(dataDir=ledger_data_dir,
                                logName=storage_name,
                                ensureDurability=True,
                                open=True,
                                config=config,
                                read_only=True)


def get_additional_storages(ledger_data_dir):
    additional_storages = \
        [name[:name.find(postfix)] for name in os.listdir(ledger_data_dir) if postfix in name]
    return additional_storages


def print_txns(storage, args):
    serializer = None
    if args.serializer == 'json':
        serializer = JsonSerializer()
    else:
        print("Unknown serializer for output: {}".format(args.serializer))
        exit()

    # --count
    count = args.count
    if count:
        print_count(storage)
        return

    # --seq_no
    seq_no = args.seq_no
    if seq_no:
        print_by_seq_no(storage, seq_no, serializer)
        return

    # print all (--from --to)
    print_all(storage, serializer)


def print_by_seq_no(storage, seq_no, serializer):
    try:
        txn = storage.get(seq_no)
    except KeyError:
        print('No transactions found for seq_no={}'.format(seq_no))
        return
    txn = serializer.serialize(txn, toBytes=False)
    print(txn)
    return


def print_count(storage):
    print(storage.size)


def print_all(storage, serializer):
    frm = int(args.frm) if args.frm else None
    to = int(args.to) if args.to else None
    for seqNo, txn in storage.iterator(start=frm, end=to):
        txn = ledger_txn_serializer.deserialize(txn)
        print(serializer.serialize(txn, toBytes=False))


def make_copy_of_ledger(data_dir):
    read_copy_data_dir = data_dir + '-read-copy'
    if os.path.exists(read_copy_data_dir):
        shutil.rmtree(read_copy_data_dir)
    shutil.copytree(data_dir, read_copy_data_dir)
    return read_copy_data_dir


if __name__ == '__main__':
    args = read_args()
    config = getConfig()

    ledger_data_dir = get_ledger_dir(args.node_name, args.network)
    read_copy_ledger_data_dir = None
    try:
        # RocksDB supports real read-only mode and does not need to have a ledger copy.
        if config.hashStore['type'].lower() != HS_ROCKSDB:
            config.db_transactions_config = None
            # NOTE: such approach works well only for small ledgers.
            tmp = make_copy_of_ledger(ledger_data_dir)

            # Let's be paranoid to avoid removing of ledger instead of its copy.
            ledger_path = Path(ledger_data_dir)
            ledger_copy_path = Path(tmp)
            assert ledger_path != ledger_copy_path
            assert ledger_copy_path not in ledger_path.parents

            read_copy_ledger_data_dir = tmp
            ledger_data_dir = read_copy_ledger_data_dir
        elif config.db_transactions_config is not None:
            # This allows to avoid debug logs creation on each read_ledger run
            config.db_transactions_config['db_log_dir'] = '/dev/null'
        storage = get_storage(args.type, ledger_data_dir)
        print_txns(storage, args)
    finally:
        if read_copy_ledger_data_dir:
            shutil.rmtree(read_copy_ledger_data_dir)